#include "dev/disk.h"
#include "tools/log.h"
#include "tools/klib.h"
#include "comm/cpu_instr.h"
#include "cpu/irq.h"
#include "dev/dev.h"

static disk_t disk_buf[DISK_CNT];
static mutex_t mutex;
static sem_t   op_sem;
static int task_on_op;


//控制     
static void ata_send_cmd (disk_t * disk, uint32_t start_sector, uint32_t sector_count, int cmd) 
{
    outb(DISK_DRIVE(disk), DISK_DRIVE_BASE | disk->drive);		    // 使用LBA寻址，并设置驱动器

	// 必须先写高字节
	outb(DISK_SECTOR_COUNT(disk), (uint8_t) (sector_count >> 8));	// 扇区数高8位
	outb(DISK_LBA_LO(disk), (uint8_t) (start_sector >> 24));		// LBA参数的24~31位
	outb(DISK_LBA_MID(disk), 0);									// 高于32位不支持
	outb(DISK_LBA_HI(disk), 0);										// 高于32位不支持
	outb(DISK_SECTOR_COUNT(disk), (uint8_t) (sector_count));		// 扇区数量低8位
	outb(DISK_LBA_LO(disk), (uint8_t) (start_sector >> 0));			// LBA参数的0-7
	outb(DISK_LBA_MID(disk), (uint8_t) (start_sector >> 8));		// LBA参数的8-15位
	outb(DISK_LBA_HI(disk), (uint8_t) (start_sector >> 16));		// LBA参数的16-23位

	// 选择对应的主-从磁盘
	outb(DISK_CMD(disk), (uint8_t)cmd);
}

static  void ata_read_data (disk_t * disk, void * buf, int size) 
{
    uint16_t * c = (uint16_t *)buf;
    for (int i = 0; i < size / 2; i++) 
    {
        *c++ = inw(DISK_DATA(disk));
    }
}

static void ata_write_data (disk_t* disk, void * buf, int size) 
{
    uint16_t * c = (uint16_t *)buf;
    for (int i = 0; i < size / 2; i++) 
    {
        outw(DISK_DATA(disk), *c++);
    }
}

//等待
static inline int ata_wait_data (disk_t * disk) 
{
    uint8_t status;
	do {
        // 等待数据或者有错误
        status = inb(DISK_STATUS(disk));
        if ((status & (DISK_STATUS_BUSY | DISK_STATUS_DRQ | DISK_STATUS_ERR))!= DISK_STATUS_BUSY) 
        {
            break;
        }
    }while (1);

    // 检查是否有错误
    return (status & DISK_STATUS_ERR) ? -1 : 0;
}


//打印磁盘信息
static void print_disk_info (disk_t * disk) 
{
    print_log("%s:", disk->name);
    print_log("  port_base: %x", disk->port_base);
    print_log("  total_size: %d m", disk->sector_count * disk->sector_size / 1024 /1024);
    print_log("  drive: %s", disk->drive == DISK_DISK_MASTER ? "Master" : "Slave");

    // 显示分区信息
    print_log("  Part info:");
    for (int i = 0; i < DISK_PRIMARY_PART_CNT; i++) 
    {
        partinfo_t * part_info = disk->partinfo + i;
        if (part_info->type != FS_INVALID) {
            print_log("    %s: type: %x, start sector: %d, count %d",
                    part_info->name, part_info->type,
                    part_info->start_sector, part_info->total_sector);
        }
    }
}


//获取指定序号的分区信息
static int detect_part_info(disk_t * disk) 
{
    mbr_t mbr;

    // 读取mbr区 0号扇区
    ata_send_cmd(disk, 0, 1, DISK_CMD_READ);
    int err = ata_wait_data(disk);
    if (err < 0) {
        print_log("read mbr failed");
        return err;
    }
    ata_read_data(disk, &mbr, sizeof(mbr));

	//遍历4个主分区描述，不考虑支持扩展分区
	part_item_t * item = mbr.part_item;
    partinfo_t * part_info = disk->partinfo + 1;
	for (int i = 0; i < MBR_PRIMARY_PART_NR; i++, item++, part_info++) 
    {
		part_info->type = item->system_id;

        // 没有分区，清空part_info
		if (part_info->type == FS_INVALID) 
        {
			part_info->total_sector = 0;
            part_info->start_sector = 0;
            part_info->disk = (disk_t *)0;
        } 
        else 
        {
            // 在主分区中找到，复制信息
            kernel_sprintf(part_info->name, "%s%d", disk->name, i + 1);
            part_info->start_sector = item->relative_sectors;
            part_info->total_sector = item->total_sectors;
            part_info->disk = disk;
        }
	}
}


//检测磁盘相关的信息
static int identify_disk (disk_t * disk) 
{
    ata_send_cmd(disk, 0, 0, DISK_CMD_IDENTIFY);

    //检测状态，如果为0，则控制器不存在
    int err = inb(DISK_STATUS(disk));
    if (err == 0) 
    {
        print_log("%s doesn't exist\n", disk->name);
        return -1;
    }

    err = ata_wait_data(disk);
    if (err < 0) 
    {
        print_log("disk[%s]: read failed!\n", disk->name);
        return err;
    }

    // 读取返回的数据，特别是uint16_t 100 through 103
    // 测试用的盘： 总共102400 = 0x19000， 实测会多一个扇区，为vhd磁盘格式增加的一个扇区
    uint16_t buf[256];
    ata_read_data(disk, buf, sizeof(buf));
    //设置扇区数
    disk->sector_count = *(uint32_t *)(buf + 100);
    disk->sector_size = SECTOR_SIZE;            // 固定为512字节大小

    // 分区0保存了整个磁盘的信息 将整个磁盘看作一个分区
    partinfo_t * part = disk->partinfo + 0;
    part->disk = disk;
    kernel_sprintf(part->name, "%s%d", disk->name, 0);
    part->start_sector = 0;
    part->total_sector = disk->sector_count;
    part->type = FS_INVALID;

    // 接下来识别硬盘上的分区信息
    detect_part_info(disk);
    return 0;
}




void disk_init()
{
    sem_init(&op_sem,0);

    mutex_init(&mutex);

    for (int i = 0; i < DISK_PER_CHANNEL; i++) 
    {
        disk_t * disk = disk_buf + i;

        // 先初始化各字段
        kernel_sprintf(disk->name, "sd%c", i + 'a') ;   // sda sdb ..... 

        disk->drive = (i == 0) ? DISK_DISK_MASTER : DISK_DISK_SLAVE;       // 主/从
        disk->port_base = IOBASE_PRIMARY;
        disk->mutex=&mutex;
        disk->op_sem=&op_sem;

        // 识别磁盘，有错不处理，直接跳过
        int err = identify_disk(disk);
        if (err == 0)
        {
            print_disk_info(disk);
        }
    }
}


//打开磁盘设备
int disk_open (device_t * dev) 
{
    //磁盘号
    int disk_idx = (dev->minor >> 4) - 0xa;
    //分区号
    int part_idx = dev->minor & 0xF;   

    if ((disk_idx >= DISK_CNT) || (part_idx >= DISK_PRIMARY_PART_CNT)) 
    {
        print_log("device minor error: %d", dev->minor);
        return -1;
    }

    disk_t * disk = disk_buf + disk_idx;
    if (disk->sector_size == 0) 
    {
        print_log("disk not exist. device:sd%x", dev->minor);
        return -1;
    }

    partinfo_t * part_info = disk->partinfo + part_idx;
    if (part_info->total_sector == 0) 
    {
        print_log("part not exist. device:sd%x", dev->minor);
        return -1;
    }

    // 磁盘存在，建立关联
    dev->data = part_info;
    irq_install(IRQ14_HARDDISK_PRIMARY, (uint32_t)exception_handler_ide_primary);
    int_open(IRQ14_HARDDISK_PRIMARY);
    return 0;
}

//读磁盘
int disk_read (device_t * dev, int start_sector, char * buf, int count) 
{
    // 取分区信息
    partinfo_t * part_info = (partinfo_t *)dev->data;
    if (!part_info) 
    {
        print_log("Get part info failed! device = %d", dev->minor);
        return -1;
    }

    disk_t * disk = part_info->disk;
    if (disk == (disk_t *)0) 
    {
        print_log("No disk for device %d", dev->minor);
        return -1;
    }

    mutex_lock(disk->mutex);
    task_on_op = 1;

    int cnt;
    ata_send_cmd(disk, part_info->start_sector + start_sector, count, DISK_CMD_READ);
    for (cnt = 0; cnt < count; cnt++, buf += disk->sector_size) 
    {
        // 利用信号量等待中断通知，然后再读取数据
        if(cur_task())
            sem_wait(disk->op_sem);

        int err = ata_wait_data(disk);
        if (err < 0) {
            print_log("disk(%s) read error: start sect %d, count %d", disk->name, start_sector, count);
            break;
        }

        //读取数据
        ata_read_data(disk, buf, disk->sector_size);
    }

    mutex_unlock(disk->mutex);
    return cnt;
}

//写扇区
int disk_write (device_t * dev, int start_sector, char * buf, int count) 
{
    partinfo_t * part_info = (partinfo_t *)dev->data;
    if (!part_info) 
    {
        print_log("Get part info failed! device = %d", dev->minor);
        return -1;
    }

    disk_t * disk = part_info->disk;
    if (disk == (disk_t *)0) 
    {
        print_log("No disk for device %d", dev->minor);
        return -1;
    }

    mutex_lock(disk->mutex);
    task_on_op = 1;

    int cnt;
    ata_send_cmd(disk, part_info->start_sector + start_sector, count, DISK_CMD_WRITE);
    for (cnt = 0; cnt < count; cnt++, buf += disk->sector_size) 
    {
        ata_write_data(disk, buf, disk->sector_size);

        //等待写完成
        sem_wait(disk->op_sem);

        int err = ata_wait_data(disk);
        if (err < 0) {
            print_log("disk(%s) write error: start sect %d, count %d", disk->name, start_sector, count);
            break;
        }
    }

    mutex_unlock(disk->mutex);
    return cnt;
}

//向磁盘发命令
int disk_control (device_t * dev, int cmd, int arg0, int arg1) 
{
    return 0;
}

//关闭磁盘
void disk_close (device_t * dev) 
{
}

//磁盘主通道中断处理
void do_handler_ide_primary(exception_information* frame)  
{
    pic_send_eoi(IRQ14_HARDDISK_PRIMARY);
    if(task_on_op && cur_task())
    {
        sem_notify(&op_sem);
    }
}

dev_desc_t dev_disk_desc = {
    .name = "disk",
	.major = DEV_DISK,
	.open = disk_open,
	.read = disk_read,
	.write = disk_write,
	.control = disk_control,
	.close = disk_close
};